feat(search): add Exa Search API as a search provider#547
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Exa (exa-api) as an additional API-backed search provider within the existing search provider abstraction, including config wiring and debug logging improvements for API providers.
Changes:
- Added
exa-apiprovider support (provider factory, provider implementation, config keys, and server/CLI key wiring). - Introduced debug logging for API providers (request body + abbreviated response sample).
- Expanded unit tests to cover Exa provider behavior and debug logging behavior.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/server/src/taskRunner.ts | Adds exa-api to request typing, validates server EXA key presence, and selects a provider-specific search API key. |
| packages/core/test/search/searchProvider.test.ts | Adds Exa provider tests and debug logging tests for both API providers. |
| packages/core/test/config.test.ts | Updates config key coverage to include exa_api_key. |
| packages/core/src/webAgent.ts | Validates exa-api requires a key and forwards debug to SearchService.create(...). |
| packages/core/src/search/searchProvider.ts | Extends provider factory to support exa-api and a debug option. |
| packages/core/src/search/providers/parallelSearch.ts | Adds gated debug logging and abbreviated response sampling via helper. |
| packages/core/src/search/providers/exaSearch.ts | New Exa search provider implementation with highlights opt-in and debug logging. |
| packages/core/src/search/debugPreview.ts | Adds abbreviateForDebug helper to truncate long strings in debug samples. |
| packages/core/src/config/defaults.ts | Adds exa-api to SEARCH_PROVIDERS and introduces exa_api_key config field (env/CLI). |
| packages/cli/src/commands/run.ts | Selects search API key based on provider (now including exa-api). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| searchProvider, | ||
| searchApiKey: | ||
| searchProvider === "exa-api" ? serverConfig.exa_api_key : serverConfig.parallel_api_key, | ||
| tabstackApiKey: body.tabstackApiKey ?? serverConfig.tabstack_api_key, |
There was a problem hiding this comment.
Fixed in 915f816 — searchApiKey is now undefined for browser providers and none; only parallel-api and exa-api get their respective key.
| searchProvider: options.searchProvider ?? cfg.search_provider, | ||
| searchApiKey: cfg.parallel_api_key, | ||
| searchApiKey: | ||
| (options.searchProvider ?? cfg.search_provider) === "exa-api" | ||
| ? cfg.exa_api_key | ||
| : cfg.parallel_api_key, |
There was a problem hiding this comment.
Fixed in 915f816 — searchApiKey is now undefined for browser providers and none; only parallel-api and exa-api get their respective key.
Add exa-api alongside the existing parallel-api/google/bing/duckduckgo providers. ExaSearchProvider POSTs to api.exa.ai/search, opting into contents.highlights so results include snippets (Exa returns metadata only by default), and maps url/title/highlights through the shared markdown + security-wrapper path identical to the Parallel provider. Wiring: - config: add "exa-api" to SEARCH_PROVIDERS and an exa_api_key field (env EXA_API_KEY, --exa-api-key) - factory + webAgent: exa-api case and key-required validation guard - run.ts / taskRunner.ts: select the API key by provider, passing undefined for providers that don't use one (browser providers, none) so an unrelated key isn't threaded through the agent config Debug logging for both API providers (Exa and Parallel), gated on --debug via the [X:debug] console.warn convention: - request: the exact outbound body (query + options, API key omitted) - response: result count plus an abbreviated sample of the first result so all returned fields are visible (long strings truncated by the shared abbreviateForDebug helper) The debug flag threads through CreateSearchProviderOptions, which SearchService.create already forwards. Tests cover the factory, markdown formatting, empty/missing-title results, API error, the highlights opt-in, and debug request/response logging on and off for both API providers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
915f816 to
33e7da8
Compare
Summary
Adds Exa (
exa-api) as a search provider alongside the existingparallel-api/google/bing/duckduckgoproviders. It slots into the existing provider abstraction — no changes to thewebSearchtool,SearchService, or the agent loop.ExaSearchProviderPOSTs tohttps://api.exa.ai/search, opting intocontents.highlightsso results include snippets (Exa returns metadata only by default), and mapsurl/title/highlightsthrough the same markdown + security-wrapper path as the Parallel provider.Changes
config/defaults.ts):"exa-api"added toSEARCH_PROVIDERS; newexa_api_keyfield (envEXA_API_KEY, CLI--exa-api-key).exa-apicase increateSearchProvider; key-required validation guard mirroringparallel-api.run.ts,taskRunner.ts): the agent exposes a single provider-agnosticsearchApiKey, so call sites now pickexa_api_keyvsparallel_api_keyby provider. The server also gainedexa-apiin its inline provider union and a key-required validation branch.--debug, via the[X:debug]console.warn convention):abbreviateForDebughelper)Note: highlights-only, text/summary intentionally not enabled
Exa can also return
text(full page markdown) andsummary(an LLM-generated abstract) viacontents.text/contents.summary. This PR intentionally requests onlyhighlightsto keep parity with the Parallel provider'sexcerptsand avoid the extra token/cost overhead (Exa bills forsummarygeneration). The debug response sample confirms Exa otherwise returnsid/image/faviconbeyond what we map; Parallel returns onlyurl/title/excerpts. Enabling text/summary later is a one-line change in the request body, ideally behind a config knob.Testing
pilo run ... --search-provider exa-api --debug), confirming the search step, key wiring, and debug output.🤖 Generated with Claude Code